﻿
/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.
This code is licensed under the Visual Studio SDK license terms.
THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/

using System;
using System.Runtime.InteropServices;
using System.CodeDom.Compiler;
using System.CodeDom;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using Microsoft.Win32;
using Microsoft.VisualStudio.Shell;
using VSLangProj80;
using System.Diagnostics;
using CodeGenerator.Mvvm.ViewModelGenerator.Serialization;
using System.Xml.Serialization;
using System.Xml.Linq;

namespace CodeGenerator.Mvvm.ViewModelGenerator
{
    /// <summary>
    /// This is the generator class. 
    /// When setting the 'Custom Tool' property of a C#, VB, or J# project item to "ViewModelGenerator", 
    /// the GenerateCode function will get called and will return the contents of the generated file 
    /// to the project system
    /// </summary>
    [ComVisible(true)]
    [Guid("70AFD83A-45AD-4221-895C-FF841073DD39")]
    [ProgId("CodeGeneratorMVVM.ViewModelGenerator")]
    [CodeGeneratorRegistration(typeof(ViewModelClassGenerator), "C# ViewModel Generator", vsContextGuids.vsContextGuidVCSProject, GeneratesDesignTimeSource = true)]
    [CodeGeneratorRegistration(typeof(ViewModelClassGenerator), "VB ViewModel Generator", vsContextGuids.vsContextGuidVBProject, GeneratesDesignTimeSource = true)]    
    [ProvideObject(typeof(ViewModelClassGenerator))]
    public class ViewModelClassGenerator : BaseCodeGeneratorWithSite
    {


#pragma warning disable 0414
        //The name of this generator (use for 'Custom Tool' property of project item)
        internal static string name = "ViewModelGenerator";
#pragma warning restore 0414

        /// <summary>
        /// Function that builds the contents of the generated file based on the contents of the input file
        /// </summary>
        /// <param name="inputFileContent">Content of the input file</param>
        /// <returns>Generated file as a byte array</returns>
        protected override byte[] GenerateCode(string inputFileContent)
        {
            ViewModelGenerationOptions generationOptions = default(ViewModelGenerationOptions);


            XmlViewModelDeserializer deserializer = new XmlViewModelDeserializer();

            DeserializationResults results = deserializer.Deserialize(inputFileContent);

            WriteErrors(results);

            //Returning null signifies that generation has failed            
            if (results.IsError)
            {
                return null;
            }

            // capture the deserialized options
            generationOptions = results.GenerationOptions;


            CodeDomProvider provider = GetCodeProvider();           

            try
            {

                ProjectItemInfo projInfo = GetEdmxFile(generationOptions);


                if (projInfo == null)
                {
                    // Could not locate edmx requested. Exit.
                    return null;
                }

                //Create the CodeCompileUnit from the passed-in XML file
                CodeCompileUnit compileUnit = SourceCodeGenerator.CreateCodeCompileUnit(generationOptions, projInfo, FileNameSpace);


                projInfo.EnsureReference();

                if (this.CodeGeneratorProgress != null)
                {
                    //Report that we are 1/2 done
                    this.CodeGeneratorProgress.Progress(50, 100);
                }

                return WriteCodeToArray(provider, compileUnit);
            }
            catch (Exception e)
            {
                this.GeneratorError(4, e.ToString(), 1, 1);
                //Returning null signifies that generation has failed
                return null;
            }
        }

        private void WriteErrors(DeserializationResults results)
        {
            // Write warnings to the output window
            foreach (DeserializationWarning warning in results.Warnings)
            {
                this.GeneratorError(1, warning.Warning, warning.Line, warning.LinePosition);
            }

            // check for an exception and exit if there is
            if (results.DeserializationException != null)
            {
                this.GeneratorError(4, results.DeserializationException.ToString(), 1, 1);
            }
        }

        private ProjectItemInfo GetEdmxFile(ViewModelGenerationOptions generationOptions)
        {
            ProjectItemInfo projectInfo;
            EnvDTE.Project edmxProject = default(EnvDTE.Project);

            if(!string.IsNullOrEmpty(generationOptions.EntityFrameworkProject))
            {
                // locate the project requested             
                edmxProject = GetProject(generationOptions.EntityFrameworkProject);

                if (edmxProject == default(EnvDTE.Project))
                {
                    this.GeneratorError(4, string.Format("Project {0} not found.", generationOptions.EntityFrameworkProject), 0, 0);
                    return null;
                }
            }
            else
            {
                // use the current project
                edmxProject = GetProject();
            }

            projectInfo = this.GetProjectItem(edmxProject, generationOptions.EntityFrameworkFile);
 
            if (projectInfo.ProjectItem == null)
            {
                this.GeneratorError(4, string.Format("File {0} not found  in project {1}.", generationOptions.EntityFrameworkFile, generationOptions.EntityFrameworkProject), 0, 0);
                return null;
            }

            return projectInfo;
        }


        private byte[] WriteCodeToArray(CodeDomProvider provider, CodeCompileUnit compileUnit)
        {
            using (StringWriter writer = new StringWriter(new StringBuilder()))
            {
                CodeGeneratorOptions options = new CodeGeneratorOptions();
                options.BlankLinesBetweenMembers = true;                
                options.BracingStyle = "C";

                //Generate the code
                provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);

                if (this.CodeGeneratorProgress != null)
                {
                    //Report that we are done
                    this.CodeGeneratorProgress.Progress(100, 100);
                }
                writer.Flush();

                //Get the Encoding used by the writer. We're getting the WindowsCodePage encoding, 
                //which may not work with all languages
                Encoding enc = Encoding.GetEncoding(writer.Encoding.WindowsCodePage);

                //Get the preamble (byte-order mark) for our encoding
                byte[] preamble = enc.GetPreamble();
                int preambleLength = preamble.Length;

                //Convert the writer contents to a byte array
                byte[] body = enc.GetBytes(writer.ToString());

                //Prepend the preamble to body (store result in resized preamble array)
                Array.Resize<byte>(ref preamble, preambleLength + body.Length);
                Array.Copy(body, 0, preamble, preambleLength, body.Length);

                //Return the combined byte array
                return preamble;
            }
        }

        /// <summary>
        /// Extracts CSDL content from the EDMX file content and returns it as an XElement
        /// </summary>
        /// <param name="inputFileContent">EDMX file conmtent</param>
        /// <returns>CSDL content XElement</returns>
        private XElement ExtractCsdlContent(string inputFileContent)
        {
            XElement csdlContent = null;
            XNamespace edmxns = "http://schemas.microsoft.com/ado/2007/06/edmx";
            XNamespace edmns = "http://schemas.microsoft.com/ado/2006/04/edm";

            XDocument edmxDoc = XDocument.Load(new StringReader(inputFileContent));
            if (edmxDoc != null)
            {
                XElement edmxNode = edmxDoc.Element(edmxns + "Edmx");
                if (edmxNode != null)
                {
                    XElement runtimeNode = edmxNode.Element(edmxns + "Runtime");
                    if (runtimeNode != null)
                    {
                        XElement conceptualModelsNode = runtimeNode.Element(edmxns + "ConceptualModels");
                        if (conceptualModelsNode != null)
                        {
                            csdlContent = conceptualModelsNode.Element(edmns + "Schema");
                        }
                    }
                }
            }
            return csdlContent;
        }
    }
}